#include <basic/errcode.h>
#include <basic/datetime.h>

struct _date_in
{/*{{{*/
    int yyyy;
    int mm;
    int dd;
};/*}}}*/

struct _time_in
{/*{{{*/
    int hh;
    int mm;
    int ss;
};/*}}}*/

int month_days(int yyyy, int mm)
{/*{{{*/
    switch(mm)
    {
      case 1:
      case 3:
      case 5:
      case 7:
      case 8:
      case 10:
      case 12: return(31);
      case 4:
      case 6:
      case 9:
      case 11: return(30);
      case 2:  return(IS_LEAP_YEAR(yyyy)?29:28);
      default: return(-1);
    }
}/*}}}*/

static int _date_str_to_in(const char *date_str,
        struct _date_in *date_in)
{/*{{{*/
    char buf[10];

    strncpy(buf, date_str, 4);  buf[4]=ZERO;
    date_in->yyyy=atoi(buf);
    strncpy(buf, date_str+4, 2);  buf[2]=ZERO;
    date_in->mm=atoi(buf);
    strncpy(buf, date_str+4+2, 2);  buf[2]=ZERO;
    date_in->dd=atoi(buf);
    if(date_in->yyyy <= 0 || date_in->mm <= 0 || date_in->dd <= 0)
        return(-1);
    return(0);
}/*}}}*/

static long _date_str_to_long(char *date_str)
{/*{{{*/
    struct _date_in date_in;
    long result;
    int i;

    if(_date_str_to_in(date_str, &date_in) || date_in.yyyy < 1900)
        return(-1);

    result=(date_in.yyyy-1900)*365;
    for(i=1900; i<date_in.yyyy; i++)
        if(IS_LEAP_YEAR(i))  result++;
    for(i=1; i<date_in.mm; i++)
        result+=month_days(date_in.yyyy, i);
    result+=date_in.dd-1;

    return(result);
}/*}}}*/

static int _date_long_to_str(long date_long, char *date_str)
{/*{{{*/
    struct _date_in date_in;
    int i;

    if(date_long < 0)  return(-1);

    i=1900;
    while(1)
    {
        if(date_long-(IS_LEAP_YEAR(i)?366:365) < 0)
            break;
        date_long-=(IS_LEAP_YEAR(i)?366:365);
        i++;
    }
    date_in.yyyy=i;

    i=1;
    while(1)
    {
        if(date_long-month_days(date_in.yyyy, i) < 0)
            break;
        date_long-=month_days(date_in.yyyy, i);
        i++;
    }
    date_in.mm=i;

    date_in.dd=date_long+1;
    sprintf(date_str, "%04d%02d%02d",
            date_in.yyyy, date_in.mm, date_in.dd);

    return(0);
}/*}}}*/

static int _time_str_to_in(char *time_str, struct _time_in *time_in)
{/*{{{*/
    char buf[10];

    buf[2]=ZERO;
    strncpy(buf, time_str, 2);
    time_in->hh=atoi(buf);
    strncpy(buf, time_str+2, 2);
    time_in->mm=atoi(buf);
    strncpy(buf, time_str+4, 2);
    time_in->ss=atoi(buf);

    return(0);
}/*}}}*/

static long _time_str_to_long(char *time_str)
{/*{{{*/
    struct _time_in time_in;

    _time_str_to_in(time_str, &time_in);

    if(time_in.hh >= 24 || time_in.mm >= 60 || time_in.ss >= 60)
      return(-1);

    return(time_in.hh*SECOND_OF_HOUR(1)+
            time_in.mm*SECOND_OF_MINUTE(1)+
            time_in.ss);
}/*}}}*/

static int _time_long_to_str(long time_long, char *time_str)
{/*{{{*/
   if(time_long < 0)  return(-1);

   sprintf(time_str, "%02ld%02ld%02ld",
           time_long / SECOND_OF_HOUR(1),
           (time_long % SECOND_OF_HOUR(1)) / SECOND_OF_MINUTE(1),
           time_long % SECOND_OF_MINUTE(1));
   return(0);
}/*}}}*/

int curr_date(char *curr_dt)
{/*{{{*/
    struct tm now;
    time_t loc;

    time(&loc);
    if(curr_dt)
    {
        localtime_r(&loc, &now);
        strftime(curr_dt, DATE_SIZE+1, DATE_DEFAULT_FMT, &now);
    }
	return(loc / SECOND_OF_DAY(1));
}/*}}}*/

char *curr_date_str(char *curr_dt)
{/*{{{*/
    struct tm now;
    time_t loc;

    if(curr_dt)
    {
        time(&loc);
        localtime_r(&loc, &now);
        curr_dt[0]=ZERO;
        strftime(curr_dt, DATE_SIZE+1, DATE_DEFAULT_FMT, &now);
    }
	return(curr_dt);
}/*}}}*/

time_t curr_timestamp(char *curr_ts)
{/*{{{*/
    struct tm now;
    time_t loc;

    time(&loc);
    if(curr_ts)
    {
        curr_ts[0]=ZERO;
        localtime_r(&loc, &now);
        strftime(curr_ts, TIMESTAMP_SIZE+1, TIMESTAMP_DEFAULT_FMT,
                &now);
    }
    return(loc);
}/*}}}*/

char *curr_timestamp_str(char *curr_ts)
{/*{{{*/
    struct tm now;
    time_t loc;

    if(curr_ts)
    {
        time(&loc);
        localtime_r(&loc, &now);
        curr_ts[0]=ZERO;
        strftime(curr_ts, TIMESTAMP_SIZE+1, TIMESTAMP_DEFAULT_FMT,
                &now);
    }
    return(curr_ts);
}/*}}}*/

int diff_date(char *begin_date, char *end_date, long *diff_days)
{/*{{{*/
    long d1, d2;

    if((d1=_date_str_to_long(begin_date)) < 0)
        return(-1);
    if((d2=_date_str_to_long(end_date)) < 0)
      return(-1);

    *diff_days=d2-d1;
    return(0);
}/*}}}*/

int diff_timestamp(char *begin_ts, char *end_ts, long *diff_secs)
{/*{{{*/
    long day1, day2;
    long sec1, sec2;
    char date1[DATE_SIZE+1], date2[DATE_SIZE+1];
    char time1[TIME_SIZE+1], time2[TIME_SIZE+1];

    strncpy(date1, begin_ts, DATE_SIZE);
    date1[DATE_SIZE]=ZERO;
    strncpy(time1, begin_ts+DATE_SIZE, TIME_SIZE);
    time1[TIME_SIZE]=ZERO;
    strncpy(date2, end_ts, DATE_SIZE);
    date2[DATE_SIZE]=ZERO;
    strncpy(time2, end_ts+DATE_SIZE, TIME_SIZE);
    time2[TIME_SIZE]=ZERO;

    if((day1=_date_str_to_long(date1)) < 0 ||
            (sec1=_time_str_to_long(time1)) < 0 ||
            (day2=_date_str_to_long(date2)) < 0 ||
            (sec2=_time_str_to_long(time2)) < 0)
        return(-1);
    *diff_secs=(day2-day1)*SECOND_OF_DAY(1)+sec2-sec1;

    return(0);
}/*}}}*/

char *change_date(char *init_dt, long days,
    char *result_dt)
{/*{{{*/
    long day1, day2;

    if(!init_dt)    return(NULL);

    if(!result_dt)
        ALLOC_MULTI_RETERR(result_dt, char, DATE_SIZE+1, NULL);

    if((day1=_date_str_to_long(init_dt)) < 0)
        return(NULL);
    day2=day1+days;
    if(_date_long_to_str(day2, result_dt))
        return(NULL);
    return(result_dt);
}/*}}}*/

char *change_timestamp(char *init_ts, long secs, char *result_ts)
{/*{{{*/
    long day, days;
    long sec;
    char buf[TIMESTAMP_SIZE+1];

    if(!init_ts)    return(NULL);

    if(!result_ts)
        ALLOC_MULTI_RETERR(result_ts, char, TIMESTAMP_SIZE+1, NULL);

    strncpy(buf, init_ts, DATE_SIZE);  buf[DATE_SIZE]=ZERO;
    if((day=_date_str_to_long(buf)) < 0)  return(NULL);
    strncpy(buf, init_ts+DATE_SIZE, TIME_SIZE);  buf[TIME_SIZE]=ZERO;
    if((sec=_time_str_to_long(buf)) < 0)  return(NULL);
    sec+=secs;
    days=sec / SECOND_OF_DAY(1);
    sec=sec % SECOND_OF_DAY(1);
    if(sec < 0)
    {   sec+=SECOND_OF_DAY(1);  days--;  }
    day+=days;
    if(_date_long_to_str(day, buf))  return(NULL);
    strncpy(result_ts, buf, DATE_SIZE);
    if(_time_long_to_str(sec, buf))  return(NULL);
    strncpy(result_ts+DATE_SIZE, buf, TIME_SIZE);
    result_ts[TIMESTAMP_SIZE]=ZERO;
    return(result_ts);
}/*}}}*/

int check_date(char *date)
{/*{{{*/
    struct _date_in date_in;

    if(!date || strlen(date) != DATE_SIZE ||
                _date_str_to_in(date, &date_in) ||
                date_in.yyyy < 1900 || date_in.mm > 12 ||
                date_in.dd > month_days(date_in.yyyy, date_in.mm))
        return(-1);
    return(0);
}/*}}}*/

int check_time(char *time)
{/*{{{*/
    struct _time_in time_in;

    if(!time || strlen(time) != TIME_SIZE ||
            _time_str_to_in(time, &time_in) ||
            time_in.hh < 0 || time_in.hh > 24 ||
            time_in.mm < 0 || time_in.mm > 59 ||
            time_in.ss < 0 || time_in.ss > 59)
        return(-1);
    return(0);
}/*}}}*/

int check_timestamp(char *ts)
{/*{{{*/
    char date_s[DATE_SIZE+1];
    char time_s[TIME_SIZE+1];
    int res;

    if(!ts || strlen(ts) != TIMESTAMP_SIZE) return(-1);

    strncpy(date_s, ts, DATE_SIZE);
    date_s[DATE_SIZE]=ZERO;
    if((res=check_date(date_s)))
        return(res);
    strncpy(time_s, ts+DATE_SIZE, TIME_SIZE);
    time_s[TIME_SIZE]=ZERO;
    if((res=check_time(time_s)))
        return(res);

    return(0);
}/*}}}*/

char *timestamp_to_str(time_t ts, char *ts_str)
{/*{{{*/
    struct tm s_tm;

    ts_str[0]=ZERO;
    if(localtime_r(&ts, &s_tm) == NULL)  return(NULL);
    sprintf(ts_str, "%04d%02d%02d%02d%02d%02d",
            s_tm.tm_year+1900, s_tm.tm_mon+1, s_tm.tm_mday,
            s_tm.tm_hour, s_tm.tm_min, s_tm.tm_sec);
    return(ts_str); 
}/*}}}*/

time_t timestamp_from_str(char *ts_str)
{/*{{{*/
    char date_str[DATE_SIZE+1];
    char time_str[TIME_SIZE+1];
    struct _date_in date_in;
    struct _time_in time_in;
    time_t now;
    struct tm tm;

    if(!ts_str) return(0);

    strncpy(date_str, ts_str, DATE_SIZE);
    date_str[DATE_SIZE]=ZERO;
    strncpy(time_str, ts_str+DATE_SIZE, TIME_SIZE);
    time_str[TIME_SIZE]=ZERO;
    _date_str_to_in(date_str, &date_in);
    _time_str_to_in(time_str, &time_in);
    time(&now);
    localtime_r(&now, &tm);
    tm.tm_sec=time_in.ss;
    tm.tm_min=time_in.mm;
    tm.tm_hour=time_in.hh;
    tm.tm_mday=date_in.dd;
    tm.tm_mon=date_in.mm-1;
    tm.tm_year=date_in.yyyy-1900;
    return(mktime(&tm));
}/*}}}*/

int timestamp_year(char *ts_str, int *year, int *yearday)
{/*{{{*/
    time_t ts;
    struct tm s_tm;

    *year=*yearday=-1;
    ts=timestamp_from_str(ts_str);
    if(localtime_r(&ts, &s_tm) == NULL)  return(-1);

    *year=s_tm.tm_year+1900;
    *yearday=s_tm.tm_yday+1;
    return(0);
}/*}}}*/

int timestamp_week(char *ts_str, int *year, int *week, int *weekday)
{/*{{{*/
    time_t ts;
    struct tm s_tm;
    char first_ts_str[TIMESTAMP_SIZE+1];
    int first_weekday;
    long diff; 

    *year=*week=*weekday=-1;
    ts=timestamp_from_str(ts_str);
    if(localtime_r(&ts, &s_tm) == NULL)
        return(-1);
    *year=s_tm.tm_year+1900;
    *weekday=s_tm.tm_wday;
    diff=s_tm.tm_yday;

    sprintf(first_ts_str, "%.4s0101000000", ts_str);
    ts=timestamp_from_str(first_ts_str);
    if(localtime_r(&ts, &s_tm) == NULL)
        return(-1);
    first_weekday=s_tm.tm_wday;
    *week=(diff+first_weekday)/7+1;
    return(0);
}/*}}}*/

char *date_fmt(char *in_date, char *in_fmt,
        char *out_date, char *out_fmt)
{/*{{{*/
#define MAX_OUT_SIZE    30

    struct tm tm;

    if(!out_date)
        ALLOC_MULTI_RETERR(out_date, char, MAX_OUT_SIZE+1, NULL);
    if(in_fmt)
        strptime(in_date, in_fmt, &tm);
    else
        strptime(in_date, DATE_DEFAULT_FMT, &tm);
    if(out_fmt)
        strftime(out_date, MAX_OUT_SIZE, out_fmt, &tm); 
    else
        strftime(out_date, MAX_OUT_SIZE, DATE_COMMON_FMT, &tm); 
    return(out_date);
}/*}}}*/

char *timestamp_fmt(char *in_ts, char *in_fmt,
        char *out_ts, char *out_fmt)
{/*{{{*/
#define MAX_OUT_SIZE    30

    struct tm tm;

    if(!out_ts)
        ALLOC_MULTI_RETERR(out_ts, char, MAX_OUT_SIZE+1, NULL);
    if(in_fmt)
        strptime(in_ts, in_fmt, &tm);
    else
        strptime(in_ts, TIMESTAMP_DEFAULT_FMT, &tm);
    if(out_fmt)
        strftime(out_ts, MAX_OUT_SIZE, out_fmt, &tm); 
    else
        strftime(out_ts, MAX_OUT_SIZE, TIMESTAMP_COMMON_FMT, &tm); 
    return(out_ts);
}/*}}}*/

int diff_millisecond(struct timeval tv1, struct timeval tv2)
{/*{{{*/
    return((tv2.tv_sec-tv1.tv_sec)*1000+(tv2.tv_usec-tv1.tv_usec)/1000);
}/*}}}*/
